//
// GPSMapEdit
// (c) Konstantin Galichsky (kg@geopainting.com), 2006-2007
//
// Set routing graph node.
//

# include "StdAfx.h"
# include "Resource.h"
# include "SetRGNode.h"
# include "UndoRedo.h"
# include "CombinedAction.h"
# include "MovePoint.h"
# include "RGNodes.h"
# include "Types.h"
# include "Label.h"
# include "MapView.h"
# include "Globals.h"

class CSetRGNodeAction : public CEditAction {
  public:
	CSetRGNodeAction (rgn_t * _pRgn, size_t _cElement, size_t _cPoint, rgnode_t * _pRGNode) :
		m_pRgn (_pRgn),
		m_cElement (_cElement),
		m_cPoint (_cPoint),
		m_pRGNodeOld (GetRGNode (* _pRgn, _cElement, _cPoint)),
		m_pRGNodeNew (_pRGNode),
		m_bWasModified ((_pRgn->dwFlags & rgn_t::fModified) != 0)
	{}

	virtual void GetName (string_t & _str) const;
	virtual cu_t CanUndo () const {return cuYes;}
	virtual bool Do ();
	virtual void Undo ();
	virtual void Redo ();

  private:
	rgn_t *    const m_pRgn;
	size_t     const m_cElement;
	size_t     const m_cPoint;
	rgnode_t * const m_pRGNodeOld;
	rgnode_t * const m_pRGNodeNew;
	bool       const m_bWasModified;
};

void CSetRGNodeAction::GetName (string_t & _str) const {
	_str.append ("set routing node [");
	GetHintText (* m_pRgn, _str);
	_str.append ("]");
}

bool CSetRGNodeAction::Do () {
	CCritSec::CLock lock (g_cs);

	assert (m_pRgn);
	assert (m_cElement < m_pRgn->elements.size ());
	rgn_element_t & element = m_pRgn->elements [m_cElement];
	const size_t cPoints = element.points.size ();

	assert (m_cPoint < element.points.size ());
	element.pExtra->PointExtras.resize (__max (m_cPoint + 1, element.points.size ()));
	element.pExtra->PointExtras [m_cPoint].SetRGNode (m_pRGNodeNew, m_cPoint, cPoints);

	m_pRgn->RefreshAttributes (g_map);
	m_pRgn->dwFlags |= rgn_t::fModified;

	return true;
}

void CSetRGNodeAction::Undo () {
	CCritSec::CLock lock (g_cs);

	assert (m_pRgn);
	assert (m_cElement < m_pRgn->elements.size ());
	rgn_element_t & element = m_pRgn->elements [m_cElement];
	const size_t cPoints = element.points.size ();

	assert (m_cPoint < element.points.size ());
	element.pExtra->PointExtras [m_cPoint].SetRGNode (m_pRGNodeOld, m_cPoint, cPoints);

	m_pRgn->RefreshAttributes (g_map);
	if (! m_bWasModified)
		m_pRgn->dwFlags &= ~rgn_t::fModified;
}

void CSetRGNodeAction::Redo () {
	CSetRGNodeAction::Do ();
}

//////////////////////////////////////

void SetRGNode (rgn_t * _pRgn, size_t _cElement, size_t _cPoint, rgnode_t * _pRGNode) {
	assert (_pRgn);
	assert (_cElement < _pRgn->elements.size ());
	assert (_cPoint < _pRgn->elements [_cElement].points.size ());

	PerformEditAction (new CSetRGNodeAction (_pRgn, _cElement, _cPoint, _pRGNode));
}

//////////////////////////////////////

struct rep_t {
	rgn_t * pRgn;
	size_t cElement;
	size_t cPoint;
};

typedef std::list<rep_t> reps_t;

static
rgnode_t * _FindNearestNodes (const rgn_t * _pRgn, size_t _cElement, size_t _cPoint, reps_t & _reps) {
	assert (_pRgn);
	assert (_cElement < _pRgn->elements.size ());
	assert (_cPoint < _pRgn->elements [_cElement].points.size ());

	const rgn_element_t & element0 = _pRgn->elements [_cElement];
	const point_t & pt0 = _pRgn->elements [_cElement].points [_cPoint];

	const float fE = 0.5f*g_map.pTypeSet->pfGetLatGrid (g_map, element0.cLevel)*360.f/40000000.f;

	rgnode_t * pRGNodeExisting = NULL;

	rgns_t & polylines = g_map.layers [rkPolyline].rgns;
	for (rgns_t::iterator iRgn = polylines.begin (); iRgn != polylines.end (); ++ iRgn) {
		rgn_t & rgn = * iRgn;
		if (rgn.IsDeleted () || rgn.IsWTR ())
			continue;

		const size_t cElements = rgn.elements.size ();
		for (size_t cElement = 0; cElement < cElements; ++ cElement) {
			rgn_element_t & element = rgn.elements [cElement];
			if (element.bDeleted || element.cLevel != element0.cLevel)
				continue;

			const points_t & points = element.points;
			const size_t cPoints = points.size ();
			for (size_t cPoint = 0; cPoint < cPoints; ++ cPoint) {
				if (PointContainsPt (points [cPoint], pt0, fE, g_map.fCosY)) {
					_reps.push_back (rep_t ());
					rep_t & rep = _reps.back ();
					rep.pRgn     = & rgn;
					rep.cElement = cElement;
					rep.cPoint   = cPoint;

					if (pRGNodeExisting == NULL) {
						rgnode_t * const pRGNode = GetRGNode (rgn, cElement, cPoint);
						if (pRGNode)
							pRGNodeExisting = pRGNode;
					}

					// NOTE: the polyline may intersect self, but it is disbled to have second ref to the same node!
					break;
				}
			}
		}
	}

	return pRGNodeExisting;
}

bool CanConnectNodes (const rgn_t * _pRgn, size_t _cElement, size_t _cPoint) {
	CCritSec::CLock lock (g_cs);

	assert (_pRgn);
	if (! g_map.pTypeSet->pfIsRoad (* _pRgn))
		return false;

	reps_t repsNearest;
	const rgnode_t * const pRGNodeToSet = _FindNearestNodes (_pRgn, _cElement, _cPoint, repsNearest);

	if (repsNearest.size () <= 1)
		return false;

	size_t cNodesToConnect = 0;
	for (reps_t::const_iterator i = repsNearest.begin (); i != repsNearest.end (); ++ i) {
		const rep_t & rep = * i;

		rgn_t * const pRgn = rep.pRgn;
		if (pRgn == NULL)
			continue;
		const bool bIsRoad = g_map.pTypeSet->pfIsRoad (* pRgn);
		if (! bIsRoad)
			continue;

		if (pRGNodeToSet != NULL) {
			const rgnode_t * const pRGNode = GetRGNode (* pRgn, rep.cElement, rep.cPoint);
			if (pRGNode != pRGNodeToSet)
				return true;
		} else
			++ cNodesToConnect;
	}

	return cNodesToConnect >= 2;
}

void ConnectNodes (rgn_t * _pRgn, size_t _cElement, size_t _cPoint) {
	CCritSec::CLock lock (g_cs);

	assert (_pRgn);
	assert (_cElement < _pRgn->elements.size ());
	assert (_cPoint < _pRgn->elements [_cElement].points.size ());
	const point_t pt0 = _pRgn->elements [_cElement].points [_cPoint];

	// Find nearest nodes to connect and RG node.
	reps_t reps;
	rgnode_t * pRGNodeToSet = _FindNearestNodes (_pRgn, _cElement, _cPoint, reps);
	if (pRGNodeToSet == NULL)
		pRGNodeToSet = & g_map.CreateNode ();

	const int iDepth = BeginCombinedAction ();

	for (reps_t::const_iterator i = reps.begin (); i != reps.end (); ++ i) {
		const rep_t & rep = * i;
		rgn_t * const pRgn = rep.pRgn;
		if (pRgn == NULL)
			continue;
		const bool bIsRoad = g_map.pTypeSet->pfIsRoad (* pRgn);
		if (! bIsRoad)
			continue;

		// Connect node to RG node.
		SetRGNode (rep.pRgn, rep.cElement, rep.cPoint, pRGNodeToSet);

		// make sure node positions are equal.
		const point_t pt = rep.pRgn->elements [rep.cElement].points [rep.cPoint];
		if (! (pt == pt0))
			MovePoint (rep.pRgn, rep.cElement, rep.cPoint, pt0);
	}

	EndCombinedAction (iDepth, GetStr (IDS_CONNECT_NODES));

	Refresh ();
}

bool CanDisconnectNodes (const rgn_t * _pRgn, size_t _cElement, size_t _cPoint) {
	CCritSec::CLock lock (g_cs);

	reps_t reps;
	const rgnode_t * const pRGNodeToReset = _FindNearestNodes (_pRgn, _cElement, _cPoint, reps);

	if (reps.size () <= 1)
		return false;

	return pRGNodeToReset != NULL;
}

void DisconnectNodes (rgn_t * _pRgn, size_t _cElement, size_t _cPoint) {
	CCritSec::CLock lock (g_cs);

	reps_t reps;
	rgnode_t * const pRGNodeToReset = _FindNearestNodes (_pRgn, _cElement, _cPoint, reps);
	if (pRGNodeToReset == NULL)
		return;

	const int iDepth = BeginCombinedAction ();

	for (reps_t::const_iterator i = reps.begin (); i != reps.end (); ++ i) {
		const rep_t & rep = * i;

		const rgnode_t * const pRGNode = GetRGNode (* rep.pRgn, rep.cElement, rep.cPoint);
		if (pRGNode != pRGNodeToReset)
			continue;

		SetRGNode (rep.pRgn, rep.cElement, rep.cPoint, NULL);
	}

	EndCombinedAction (iDepth, GetStr (IDS_DISCONNECT_NODES));

	Refresh ();
}
